package com.llbnk.springcoding.aspect;
import com.llbnk.springcoding.annotation.RedLock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Aspect
@Slf4j
@Component
public class RedLockAspect {

    @Autowired
    private RedissonClient redisClient;

    @Pointcut("@annotation(com.llbnk.springcoding.annotation.RedLock)")
    public void redLockPointcut() {
    }

    @Around("redLockPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //1.获取切入点中的MethodSignature
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        //2.获取方法参数并保存在context
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setRootObject(signature.getMethod());
        String[] paramNames = signature.getParameterNames();
        Object[] params = joinPoint.getArgs();
        if (paramNames != null && params != null && paramNames.length == params.length) {
            for (int i = 0; i < paramNames.length; i++) {
                context.setVariable(paramNames[i], params[i]);
            }
        }

        //3.获取分布式锁注解参数
        RedLock redLock = signature.getMethod().getAnnotation(RedLock.class);
        ExpressionParser parser = new SpelExpressionParser();
        String key = (String) parser.parseExpression(redLock.key()).getValue(context);
        int waitTime = redLock.waitTime();
        int leaseTime = redLock.leaseTime();

        //4.使用redisson获取分布式锁
        RLock redisLock = redisClient.getLock(key);
        RedissonRedLock redissonRedLock = new RedissonRedLock(redisLock);

        //5.分布式锁执行业务逻辑
        if (redissonRedLock.tryLock(waitTime, leaseTime,TimeUnit.SECONDS)) {
            try {
                return joinPoint.proceed(params);
            } finally {
                redissonRedLock.unlock();
            }
        } else {
            throw new Exception("[redlock] is using...");
        }
    }
}